﻿using System;
using System.Collections;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using System.Windows.Media.Effects;
using System.Windows.Shapes;
using CommomFunctions;
using System.Windows.Media.Animation;
using Seta;

namespace Árvore_Binária_de_Pesquisa
{
    public class Animations : CommomFunctions.Animations
    {
        /// <summary>
        /// Keeps the old and the current position of a node in the tree. Thus, it's possible to know which nodes changed positions
        /// after a removal operation and animate the position changing.
        /// </summary>
        struct positions
        {
            public int oldPos;
            public int currentPos;
        }

        /// <summary>
        /// cv -> a copy of the main "ctrls" variable
        /// lblX -> a label containing the letter "X" that will be used to indicate the node that will be removed
        /// tempLabel -> an auxiliary Label that will have the properties of the Label that we want
        /// to animate. It is the Label that will be in fact transformed during animation.
        /// nodesVisited -> the positions of the nodes visited during a certain operation
        /// alreadyWroteTrace -> indicates if the trace was already written or not to avoid information duplicity
        /// removeNodeOperation -> indicates if it is a removal operation or not
        /// foundNodeToRemoveFromTree -> indicates if the node to remove from the tree was found or not
        /// nodeToRemoveFromTree -> indicates the node that we will try to remove from the tree
        /// sentenceToWrite -> the sentence that will be written to the trace
        /// colorToWrite -> the color that will be used to write a certain sentence to the trace
        /// posDeletada -> the position of the node that was removed from the tree
        /// keyPositions -> contains the new and the old positions of a Label
        /// </summary>
        private ControlVariables cv = new ControlVariables();
        private Label lblX = new Label(); 
        private Label tempLabel;
        private ArrayList nodesVisited = new ArrayList();                
        private bool alreadyWroteTrace = false;
        private bool removeNodeOperation = false;
        private bool foundNodeToRemoveFromTree = false;        
        private string nodeToRemoveFromTree = "";
        private string sentenceToWrite = "";
        private Brush colorToWrite = Brushes.Black;
        private int posDeletada;
        private Hashtable keyPositions = new Hashtable();
        
        public bool foundNodeToRemove
        {
            get { return foundNodeToRemoveFromTree; }
        }

        public bool isRemoveOperation
        {
            get { return removeNodeOperation; }
            set { removeNodeOperation = value; }
        }        

        public string nodeToRemove
        {
            get { return this.nodeToRemoveFromTree; }
            set { this.nodeToRemoveFromTree = value; }
        }
               
        public string sentence
        {
            get { return this.sentenceToWrite; }
            set { this.sentenceToWrite = value; }
        }

        public Brush color
        {
            get { return this.colorToWrite; }
            set { this.colorToWrite = value; }
        }

        public Animations()
        {

        }

        /// <summary>
        /// Copies the values of gd and cVar to their respective local copies and calls the function that creates "lblX"
        /// </summary>
        /// <param name="gd"></param>
        /// <param name="cVar"></param>
        public void createAnimations(Grid gd, ControlVariables cVar)
        {
            Gridi = gd;
            cv = cVar;
            createX();
        }

        /// <summary>
        /// Creates the Label "lblX"
        /// </summary>
        private void createX()
        {
            FuncoesVariadas.setaLinhasEColunas(lblX, Gridi, 0, 9, 1, 6);

            lblX.VerticalAlignment = VerticalAlignment.Top;
            lblX.HorizontalAlignment = HorizontalAlignment.Center;
            lblX.VerticalContentAlignment = VerticalAlignment.Center;
            lblX.FontStretch = FontStretches.ExtraExpanded;
            lblX.FontFamily = new FontFamily("Lucida");
            lblX.FontSize = 70;
            lblX.BorderThickness = new Thickness(0);
            lblX.FontWeight = FontWeights.UltraBold;
            lblX.Foreground = Brushes.Firebrick;
            lblX.Content = "X";
            lblX.Visibility = Visibility.Hidden;
            lblX.Opacity = 0.85;
        }

        /// <summary>
        /// Creates a glow effect that will be applyed to the Label that is being animated
        /// </summary>
        /// <param name="lbl"></param>
        /// <param name="size"></param>
        private void createGlowEffect(Label lbl, double size)
        {
            OuterGlowBitmapEffect myGlowEffect = new OuterGlowBitmapEffect();
            myGlowEffect.GlowColor = Colors.Pink;
            myGlowEffect.GlowSize = size;
            lbl.BitmapEffect = myGlowEffect;
        }

        /// <summary>
        /// Creates the Label that will be animated
        /// </summary>
        private void createLabelToAnimate()
        {
            Grid.SetRow(tempLabel, 1);
            Grid.SetRowSpan(tempLabel, 6);
            Grid.SetColumnSpan(tempLabel, 9);

            createGlowEffect(tempLabel, 5.0);
            tempLabel.BorderBrush = Brushes.Lavender;
            tempLabel.BorderThickness = new Thickness(3.0);
            tempLabel.Opacity = 1;
        }

        /// <summary>
        /// Creates the Label that will indicate the order in which the nodes were visited.
        /// They are created all at the beginning of the animation but the delay time controls their visibility
        /// </summary>
        /// <param name="margin">The margin of the node visited accolording to the colorresponding Label</param>
        /// <param name="order">The order in which this node was visited</param>
        /// <param name="delay">The time spent before the Label appears</param>
        /// <param name="isTraversal">Indicates if it is a traversal or another operation</param>
        /// <returns></returns>
        private Label criaQuadradoEstatico(Thickness margin, int order, int delay, bool isTraversal)
        {
            Label visitedNode = new Label();

            FuncoesVariadas.setaLinhasEColunas(visitedNode, Gridi, 0, 9, 1, 6);

            createGlowEffect(visitedNode, 10.0);

            visitedNode.MaxWidth = 110;
            visitedNode.Margin = new Thickness(margin.Left, margin.Top, margin.Right, margin.Bottom);
            visitedNode.Content = order;
            visitedNode.Opacity = 0;

            visitedNode.FontSize = 18;
            visitedNode.FontWeight = FontWeights.Bold;
            visitedNode.Foreground = Brushes.Black;

            if (isTraversal)
            {
                animateLabelOpacity(visitedNode, 0.0, 1.0, 1.5, 3 * (delay - 1));
            }
            else
            {
                animateLabelOpacity(visitedNode, 0.0, 1.0, 0.5, 1.5 * (order - 1));
            }

            return visitedNode;
        }

        /// <summary>
        /// Defines the behavior of the animation
        /// </summary>
        /// <param name="pos">Indicates the position of the Label that will be animated</param>
        /// <param name="isTraversal">Indicates if we are animating a traversal or an operation</param>
        public void defineAnimation(Array pos, bool isTraversal)
        {
            cv.disableButtonsAndMenus();

            tempLabel = new Label();
            Gridi.Children.Add(tempLabel);
            createLabelToAnimate();
            tempLabel.MaxWidth = 110;

            if (isTraversal)
            {
                sentence = "Caminho percorrido";
                color = Brushes.SpringGreen;
            }

            int cont = 0;
            while (cont < pos.GetLength(0))
            {
                if ((cv.getLabel((int)pos.GetValue(cont))).Visibility == Visibility.Visible)
                {
                    tempLabel.Margin = new Thickness((cv.getLabel((int)pos.GetValue(cont))).Margin.Left, (cv.getLabel((int)pos.GetValue(cont))).Margin.Top, (cv.getLabel((int)pos.GetValue(cont))).Margin.Right, (cv.getLabel((int)pos.GetValue(cont))).Margin.Bottom);
                    nodesVisited.Add(criaQuadradoEstatico((cv.getLabel((int)pos.GetValue(cont))).Margin, 1, 1, isTraversal));
                    break;
                }
                cont++;
            }

            ThicknessAnimationUsingKeyFrames myThicknessAnimation = new ThicknessAnimationUsingKeyFrames();
            myThicknessAnimation.FillBehavior = FillBehavior.HoldEnd;
            myThicknessAnimation.Completed += new EventHandler(myThicknessAnimation_Completed);
            
            int delay = 1;
            for (int i = cont + 1; i < pos.GetLength(0); i++)
            {
                if (cv.getLabel((int)pos.GetValue(i)).Visibility == Visibility.Visible)
                {
                    LinearThicknessKeyFrame p1_p2;
                    if (isTraversal)
                    {
                        p1_p2 = new LinearThicknessKeyFrame((cv.getLabel((int)pos.GetValue(i))).Margin, KeyTime.FromTimeSpan(TimeSpan.FromSeconds(3 * delay)));
                    }
                    else p1_p2 = new LinearThicknessKeyFrame((cv.getLabel((int)pos.GetValue(i))).Margin, KeyTime.FromTimeSpan(TimeSpan.FromSeconds(1.5 * delay)));
                    nodesVisited.Add(criaQuadradoEstatico((cv.getLabel((int)pos.GetValue(i))).Margin, delay + 1, delay + 1, isTraversal));

                    myThicknessAnimation.KeyFrames.Add(p1_p2);
                    delay++;
                }
            }
            if (isTraversal)
            {
                myThicknessAnimation.Duration = TimeSpan.FromSeconds((delay * 2.8) + 2.0);
            }
            else myThicknessAnimation.Duration = TimeSpan.FromSeconds(delay * 1.8);

            if (!isTraversal)
            {
                ArrayList positionsVisiveis = new ArrayList();

                for (int i = 0; i < pos.GetLength(0); i++)
                {
                    if (cv.getLabel((int)pos.GetValue(i)).Visibility == Visibility.Visible)
                    {
                        positionsVisiveis.Add((int)pos.GetValue(i));
                    }
                }
                for (int i = 0; i < positionsVisiveis.Count; i++)
                {
                    foreach (DictionaryEntry seta in cv.getArrows())
                    {
                        string pedaco1 = seta.Key.ToString().Substring(0, 1);
                        string pedaco2 = seta.Key.ToString().Substring(1, seta.Key.ToString().Length - 1);

                        if (pedaco2 == positionsVisiveis[i].ToString())
                        {
                            cv.addArrowToAnimate((int)seta.Value);
                            break;
                        }
                    }
                }
                double duracao = 0;
                if (cv.getArrowsToAnimate().Count != 0)
                {
                    duracao = myThicknessAnimation.Duration.TimeSpan.Seconds / cv.getArrowsToAnimate().Count;
                }

                foreach (int seta in cv.getArrowsToAnimate())
                {
                    DoubleAnimation dbAnimaSetasColoridas = new DoubleAnimation(0.0, 1.0, TimeSpan.FromSeconds(duracao));

                    dbAnimaSetasColoridas.BeginTime = TimeSpan.FromSeconds(duracao * (cv.getArrowsToAnimate().IndexOf(seta)));

                    ((ArrowLine)cv.getColoredArrow(seta)).BeginAnimation(ArrowLine.OpacityProperty, dbAnimaSetasColoridas);
                }
            }
            
            createStoryboard(myThicknessAnimation);

        }

        /// <summary>
        /// Defines the behavior of the animation when it is a traversal
        /// </summary>
        /// <param name="pos">Indicates the position of the Label that will be animated</param>
        /// <param name="addVisitedNodes">The nodes that were visited</param>
        public void defineAnimacaoCaminhamento(Array pos, Array addVisitedNodes)
        {
            cv.disableButtonsAndMenus();

            tempLabel = new Label();
            Gridi.Children.Add(tempLabel);

            createLabelToAnimate();
            tempLabel.MaxWidth = 110;

            sentence = "Caminho percorrido";
            color = Brushes.SpringGreen;

            int cont = 0;
            int ind = 1;
            while (cont < pos.GetLength(0))
            {
                if ((cv.getLabel((int)pos.GetValue(cont))).Visibility == Visibility.Visible)
                {
                    tempLabel.Margin = new Thickness((cv.getLabel((int)pos.GetValue(cont))).Margin.Left, (cv.getLabel((int)pos.GetValue(cont))).Margin.Top, (cv.getLabel((int)pos.GetValue(cont))).Margin.Right, (cv.getLabel((int)pos.GetValue(cont))).Margin.Bottom);
                    if ((bool)addVisitedNodes.GetValue(cont))
                    {
                        nodesVisited.Add(criaQuadradoEstatico((cv.getLabel((int)pos.GetValue(cont))).Margin, ind, 1, true));
                        ind++;
                    }

                    break;
                }
                cont++;
            }

            ThicknessAnimationUsingKeyFrames myThicknessAnimation = new ThicknessAnimationUsingKeyFrames();
            myThicknessAnimation.FillBehavior = FillBehavior.HoldEnd;
            myThicknessAnimation.Completed += new EventHandler(myThicknessAnimation_Completed);

            int delay = 1;

            for (int i = cont + 1; i < pos.GetLength(0); i++)
            {
                if ((cv.getLabel((int)pos.GetValue(i))).Visibility == Visibility.Visible)
                {
                    LinearThicknessKeyFrame p1_p2;
                    {
                        p1_p2 = new LinearThicknessKeyFrame((cv.getLabel((int)pos.GetValue(i))).Margin, KeyTime.FromTimeSpan(TimeSpan.FromSeconds(3 * delay)));
                    }
                    if ((bool)addVisitedNodes.GetValue(i))
                    {
                        nodesVisited.Add(criaQuadradoEstatico((cv.getLabel((int)pos.GetValue(i))).Margin, ind, delay + 1, true));
                        ind++;
                    }
                    myThicknessAnimation.KeyFrames.Add(p1_p2);
                    delay++;
                }
            }
            
            myThicknessAnimation.Duration = TimeSpan.FromSeconds((delay * 2.8) + 3.0);
            
            createStoryboard(myThicknessAnimation);

        }

        /// <summary>
        /// Creates the Storyboard that will be responsible for the animation defined by myThicknessAnimation
        /// </summary>
        /// <param name="myThicknessAnimation"></param>
        private void createStoryboard(ThicknessAnimationUsingKeyFrames myThicknessAnimation)
        {
            NameScope.SetNameScope(this.tempLabel, new NameScope());
            this.tempLabel.RegisterName("myAnimatedObject", tempLabel);
            createStoryboard(myThicknessAnimation, Label.MarginProperty, this.tempLabel);
        }

        /// <summary>
        /// Animates the Label opacity
        /// </summary>
        /// <param name="lbl"></param>
        /// <param name="startValue"></param>
        /// <param name="endValue"></param>
        /// <param name="duration"></param>
        /// <param name="beginTime"></param>
        /// <param name="animationCompleted"></param>
        private void animateLabelOpacity(Label lbl, double startValue, double endValue, double duration, double beginTime, EventHandler animationCompleted)
        {
            DoubleAnimation dbBorda = new DoubleAnimation(startValue, endValue, TimeSpan.FromSeconds(duration));
            dbBorda.BeginTime = TimeSpan.FromSeconds(beginTime);
            dbBorda.Completed += new EventHandler(animationCompleted);
            lbl.BeginAnimation(Label.OpacityProperty, dbBorda);
        }

        private void animateLabelOpacity(Label lbl, double startValue, double endValue, double duration, double beginTime)
        {
            DoubleAnimation dbBorda = new DoubleAnimation(startValue, endValue, TimeSpan.FromSeconds(duration));
            dbBorda.BeginTime = TimeSpan.FromSeconds(beginTime);
            lbl.BeginAnimation(Label.OpacityProperty, dbBorda);
        }

        /// <summary>
        /// Executes when the visiting nodes animations are completed.
        /// If it was a removal operation, it is still necessary to animate the removal of the node
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void myThicknessAnimation_Completed(object sender, EventArgs e)
        {
            animateLabelOpacity(tempLabel, 1.0, 0.0, 0.5, 0.0, dbOpacity_Completed);

            cv.updateButton(true, false);
            cv.enableMenuItems();

            if (isRemoveOperation)
            {
                isRemoveOperation = false;
                if (cv.foundNodeToRemove())
                {
                    animacaoRemocaoNodo();
                }
            }

            cv.writeTrace(sentence, color);

            foreach (Label lbl in nodesVisited)
            {
                animateLabelOpacity(lbl, 1.0, 0.0, 0.5, 0.0, dbOpacity_Completed);
            }

            foreach (int seta in cv.getArrowsToAnimate())
            {
                DoubleAnimation dbDesanimaSetasColoridas = new DoubleAnimation(1.0, 0.0, TimeSpan.FromSeconds(0));

                ((ArrowLine)cv.getColoredArrow(seta)).BeginAnimation(ArrowLine.OpacityProperty, dbDesanimaSetasColoridas);

            }

            cv.clearArrowsToAnimate();
        }

        /// <summary>
        /// Executes when the opacity animations are completed.
        /// Removes the temporary elements that were added to the grid and restore the opacity states
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void dbOpacity_Completed(object sender, EventArgs e)
        {
            Gridi.Children.Remove(tempLabel);

            animateLabelOpacity(tempLabel, 0.0, 1.0, 0.0, 0.0);

            tempLabel.ClearValue(Label.MarginProperty);
            tempLabel.SetValue(Label.MarginProperty, new Thickness(0));

            foreach (Label lbl in nodesVisited)
            {
                Gridi.Children.Remove(lbl);
            }
            nodesVisited.Clear();
        }


        /// <summary>
        /// Defines the behavior of the animation when it is a remove node operation
        /// </summary>
        private void animacaoRemocaoNodo()
        {
            alreadyWroteTrace = false;

            cv.disableButtonsAndMenus();

            int position = cv.getNodePosition(nodeToRemove);

            lblX.Visibility = Visibility.Visible;

            lblX.Margin = new Thickness((cv.getLabel(position)).Margin.Left,
                                        (cv.getLabel(position)).Margin.Top - 20,
                                        (cv.getLabel(position)).Margin.Right,
                                        (cv.getLabel(position)).Margin.Bottom - 50);

            animateLabelOpacity(lblX, 0.85, 0.0, 0.5, 1.3, dblblX_Completed);

            keyPositions.Clear();
            foreach (Label lbl in cv.getLabel())
            {
                if ((cv.getLabel(cv.getLabel().IndexOf(lbl))).Visibility == Visibility.Visible)
                {                    
                    positions pos = new positions();

                    pos.oldPos = cv.getNodePosition((cv.getLabel(cv.getLabel().IndexOf(lbl))).Content.ToString());
                    pos.currentPos = cv.getNodePosition((cv.getLabel(cv.getLabel().IndexOf(lbl))).Content.ToString());

                    if (keyPositions.ContainsKey((cv.getLabel(cv.getLabel().IndexOf(lbl))).Content.ToString()))
                    {
                        keyPositions[(cv.getLabel(cv.getLabel().IndexOf(lbl))).Content.ToString()] = pos;
                    }
                    else keyPositions.Add((cv.getLabel(cv.getLabel().IndexOf(lbl))).Content.ToString(), pos);
                }
            }

            cv.removeTreeNode(nodeToRemove);
            
            foreach (Label lbl in cv.getLabel())
            {
                if ((cv.getLabel(cv.getLabel().IndexOf(lbl))).Visibility == Visibility.Visible)
                {
                    positions pos = new positions();

                    string elemento = (cv.getLabel(cv.getLabel().IndexOf(lbl))).Content.ToString();
                    
                    foreach (DictionaryEntry chave in keyPositions)
                    {
                        if (chave.Key.ToString() == elemento)
                        {
                            pos = (positions)chave.Value;
                            break;
                        }
                    }

                    if (cv.getNodePosition(elemento) != -1)
                    {
                        pos.currentPos = cv.getNodePosition(elemento);
                    }
                    else
                    {
                        posDeletada = pos.oldPos;
                        pos.currentPos = -1;
                    }

                    keyPositions[elemento] = pos;
                }
            }
                        
            cv.setAllHidden();
                    
            foreach (DictionaryEntry chave in keyPositions)
            {
                positions pos = (positions)chave.Value;

                if (pos.currentPos != -1)
                {
                    cv.setLabelVisibility(pos.oldPos, Visibility.Visible);
                    if (pos.oldPos > 0)
                        cv.setArrowVisibility(pos.oldPos - 1, Visibility.Visible);

                    if (pos.currentPos != pos.oldPos)
                    {
                        if (pos.oldPos > 0)
                        {
                            if (pos.currentPos != posDeletada)
                            {
                                animateArrowPosition(cv.getArrow(pos.oldPos - 1),
                                                     cv.getArrowPosition(ArrowCoordinate.X1, pos.oldPos - 1),
                                                     cv.getArrowPosition(ArrowCoordinate.X1, pos.currentPos - 1),
                                                     2.0, 2.1, ArrowLine.X1Property);

                                animateArrowPosition(cv.getArrow(pos.oldPos - 1),
                                                     cv.getArrowPosition(ArrowCoordinate.X2, pos.oldPos - 1),
                                                     cv.getArrowPosition(ArrowCoordinate.X2, pos.currentPos - 1),
                                                     2.0, 2.1, ArrowLine.X2Property);

                                animateArrowPosition(cv.getArrow(pos.oldPos - 1),
                                                     cv.getArrowPosition(ArrowCoordinate.Y1, pos.oldPos - 1),
                                                     cv.getArrowPosition(ArrowCoordinate.Y1, pos.currentPos - 1),
                                                     2.0, 2.1, ArrowLine.Y1Property);

                                animateArrowPosition(cv.getArrow(pos.oldPos - 1),
                                                     cv.getArrowPosition(ArrowCoordinate.Y2, pos.oldPos - 1),
                                                     cv.getArrowPosition(ArrowCoordinate.Y2, pos.currentPos - 1),
                                                     2.0, 2.1, ArrowLine.Y2Property);

                            }
                            else animateArrowOpacity(cv.getArrow(pos.oldPos - 1), 1.0, 0.0, 1.0, 2.3);
                        }

                        animateLabelMargin(cv.getLabel(pos.oldPos), cv.getLabel(pos.currentPos), 2.0, 2.1, th_Completed);
                    }
                }
                else
                {
                    if (pos.oldPos > 0)
                    {
                        bool wasLeaf = true;
                        if (pos.oldPos < 7)
                        {
                            ICollection valores = keyPositions.Values;
                            foreach (positions p in valores)
                            {
                                if ((p.oldPos == ((pos.oldPos * 2) + 1)) || (p.oldPos == ((pos.oldPos * 2) + 2)))
                                {
                                    wasLeaf = false;
                                    break;
                                }
                            }
                        }
                        cv.setArrowVisibility(pos.oldPos - 1, Visibility.Visible);
                        if (wasLeaf)
                        {
                            animateArrowOpacity(cv.getArrow(pos.oldPos - 1), 1.0, 0.0, 1.0, 2.0);
                        }
                    }
                    cv.setLabelVisibility(pos.oldPos, Visibility.Visible);

                    animateLabelOpacity(cv.getLabel(pos.oldPos), 1.0, 0.0, 1.5, 2.1, thAnime_Completed);
                }
            }
        }

        /// <summary>
        /// Defines the animation of the nodes changing places after a node was removed
        /// </summary>
        /// <param name="oldLabel"></param>
        /// <param name="newLabel"></param>
        /// <param name="duration"></param>
        /// <param name="beginTime"></param>
        /// <param name="animationCompleted"></param>
        private void animateLabelMargin(Label oldLabel, Label newLabel, double duration, double beginTime, EventHandler animationCompleted)
        {
            ThicknessAnimation th = new ThicknessAnimation(oldLabel.Margin, newLabel.Margin, TimeSpan.FromSeconds(duration));
            th.BeginTime = TimeSpan.FromSeconds(beginTime);
            th.Completed += new EventHandler(animationCompleted);
            oldLabel.BeginAnimation(Label.MarginProperty, th);
        }

        private void animateLabelMargin(Label oldLabel, Label newLabel, double duration, double beginTime)
        {
            ThicknessAnimation th = new ThicknessAnimation(oldLabel.Margin, newLabel.Margin, TimeSpan.FromSeconds(duration));
            th.BeginTime = TimeSpan.FromSeconds(beginTime);
            oldLabel.BeginAnimation(Label.MarginProperty, th);
        }

        /// <summary>
        /// Defines the animation of the arrows changing positions after a node was removed
        /// </summary>
        /// <param name="arrow"></param>
        /// <param name="oldPosition"></param>
        /// <param name="newPosition"></param>
        /// <param name="duration"></param>
        /// <param name="beginTime"></param>
        /// <param name="dp"></param>
        private void animateArrowPosition(ArrowLine arrow, double oldPosition, double newPosition, double duration, double beginTime, DependencyProperty dp)
        {
            DoubleAnimation dbX1 = new DoubleAnimation(oldPosition, newPosition, TimeSpan.FromSeconds(duration));
            dbX1.BeginTime = TimeSpan.FromSeconds(beginTime);
            arrow.BeginAnimation(dp, dbX1);
        }

        /// <summary>
        /// Defines the animation of the arrows opacity
        /// </summary>
        /// <param name="p"></param>
        /// <param name="startValue"></param>
        /// <param name="endValue"></param>
        /// <param name="duration"></param>
        /// <param name="beginTime"></param>
        private void animateArrowOpacity(ArrowLine p, double startValue, double endValue, double duration, double beginTime)
        {
            DoubleAnimation db = new DoubleAnimation(startValue, endValue, TimeSpan.FromSeconds(duration));
            db.BeginTime = TimeSpan.FromSeconds(beginTime);
            p.BeginAnimation(ArrowLine.OpacityProperty, db);
        }

        /// <summary>
        /// Executes when the "lblX" animation is completed.
        /// Restores its original opacity.
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void dblblX_Completed(object sender, EventArgs e)
        {
            lblX.Visibility = Visibility.Hidden;
            animateLabelOpacity(lblX, 0.1, 1.0, 0.0, 0.0);
        }

        /// <summary>
        /// Executes when the Label margin animation is completed.
        /// Verifies if their positions chenged, restores the Labels and Arrows opacities and sets their visibility
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        void th_Completed(object sender, EventArgs e)
        {
            foreach (DictionaryEntry chave in keyPositions)
            {
                positions pos = (positions)chave.Value;

                if (pos.currentPos != -1)
                {
                    if (pos.currentPos != pos.oldPos)
                    {
                        cv.setLabelVisibility(pos.oldPos, Visibility.Hidden);
                        animateLabelOpacity(cv.getLabel(pos.currentPos), 0.0, 1.0, 0.0, 0.0);

                        if (pos.oldPos > 0)
                        {
                            if (pos.currentPos != posDeletada)
                            {
                                animateArrowPosition(cv.getArrow(pos.oldPos - 1),
                                                     cv.getArrowPosition(ArrowCoordinate.X1, pos.currentPos - 1),
                                                     cv.getArrowPosition(ArrowCoordinate.X1, pos.oldPos - 1),
                                                     0.0, 0.0, ArrowLine.X1Property);

                                animateArrowPosition(cv.getArrow(pos.oldPos - 1),
                                                     cv.getArrowPosition(ArrowCoordinate.X2, pos.currentPos - 1),
                                                     cv.getArrowPosition(ArrowCoordinate.X2, pos.oldPos - 1),
                                                     0.0, 0.0, ArrowLine.X2Property);

                                animateArrowPosition(cv.getArrow(pos.oldPos - 1),
                                                     cv.getArrowPosition(ArrowCoordinate.Y1, pos.currentPos - 1),
                                                     cv.getArrowPosition(ArrowCoordinate.Y1, pos.oldPos - 1),
                                                     0.0, 0.0, ArrowLine.Y1Property);

                                animateArrowPosition(cv.getArrow(pos.oldPos - 1),
                                                     cv.getArrowPosition(ArrowCoordinate.Y2, pos.currentPos - 1),
                                                     cv.getArrowPosition(ArrowCoordinate.Y2, pos.oldPos - 1),
                                                     0.0, 0.0, ArrowLine.Y2Property);
                            }

                            cv.setArrowVisibility(pos.oldPos - 1, Visibility.Hidden);
                            animateArrowOpacity(cv.getArrow(pos.oldPos - 1), 0.0, 1.0, 0.0, 0.0);

                        }

                    }


                }
                else
                {
                    //é tratado na thAnime_Completed
                }

            }

            Label tempLabel = new Label();
            for (int i = 0; i < cv.getLabel().Count; i++)
            {
                tempLabel.Margin = cv.getOriginalMargins(i);
                animateLabelMargin(cv.getLabel(i), tempLabel, 0.0, 0.0);
            }

            cv.drawTree();
            

            testAlreadyWrittenToTrace();

        }

        /// <summary>
        /// Executes when the Label margin animation is completed.
        /// Verifies if their positions chenged, restores the Labels and Arrows opacities and sets their visibility
        /// </summary>
        void thAnime_Completed(object sender, EventArgs e)
        {

            if (posDeletada > 0)
            {
                bool eraFolha = true;
                if (posDeletada < 7)
                {
                    ICollection valores = keyPositions.Values;
                    foreach (positions p in valores)
                    {
                        if ((p.oldPos == ((posDeletada * 2) + 1)) || (p.oldPos == ((posDeletada * 2) + 2)))
                        {
                            eraFolha = false;
                            break;
                        }

                    }



                }
                if (!eraFolha)
                    cv.setArrowVisibility(posDeletada - 1, Visibility.Visible);
                else cv.setArrowVisibility(posDeletada - 1, Visibility.Hidden);

                animateArrowOpacity(cv.getArrow(posDeletada - 1), 0.0, 1.0, 0.0, 0.0);

            }

            cv.setLabelVisibility(posDeletada, Visibility.Hidden);
            animateLabelOpacity(cv.getLabel(posDeletada), 0.0, 1.0, 0.0, 0.0);

            testAlreadyWrittenToTrace();


        }

        /// <summary>
        /// Tests if the information was already written to Trace
        /// </summary>
        private void testAlreadyWrittenToTrace()
        {
            if (!alreadyWroteTrace)
            {
                cv.writeTrace("Nodo removido", Brushes.SeaGreen);

                cv.updateButton(true, false);
                cv.enableMenuItems();

                alreadyWroteTrace = true;
            }
        }

    }
}
